﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using Lime49;
using System.Web;

namespace LockCrypt.Core {
    /**
 * URL which supports wildcards (*) in it's component parts
 * @author Harry Jennerway
 * @see http://leghumped.com/blog/2008/11/03/java-matching-urls-with-regex-wildcards/
 * @see http://www.lime49.com/
 */

    public class WildcardUrl {//: ISerializable {
        /// <summary>
        /// Gets or sets the protocol name of this <code>URL</code>.
        /// </summary>
        /// <value>The protocol name of this <code>URL</code>.</value>
        /// <example>http</example>
        public string Protocol { get; set; }

        /// <summary>
        /// Gets or sets the username.
        /// </summary>
        /// <value>The username.</value>
        public string User { get; set; }

        /// <summary>
        /// Gets or sets the password.
        /// </summary>
        /// <value>The password.</value>
        public string Password { get; set; }

        /// <summary>
        /// Gets the user info part of this <code>URL</code>.
        /// </summary>
        /// <returns>The user info part of this <code>URL</code></returns>
        /// <example>user:pass</example>
        public string UserInfo {
            get { return User + (Password == "*" ? string.Empty : ":" + Password); }
        }

        /// <summary>
        /// Gets or sets the host name.
        /// </summary>
        /// <value>The host name.</value>
        /// <example>www.lime49.com</example>
        public string Host { get; set; }

        /// <summary>
        /// Gets or sets the directory name.
        /// </summary>
        /// <value>The name of the directory.</value>
        /// <example>/some/directory</example>
        public string DirectoryName { get; set; }

        /// <summary>
        /// Gets or sets the name of the file.
        /// </summary>
        /// <value>The name of the file.</value>
        /// <example>file.php</example>
        public string FileName { get; set; }

        /// <summary>
        /// Gets the path part of this <code>URL</code>, or an empty string if one does not exist.
        /// </summary>
        /// <value>The path part of this <code>URL</code>, or an empty string if one does not exist.</value>
        /// <example>/some/directory/file.php</example>
        public string Path {
            get { return /*DirectoryName == "*" && FileName == "*" ? "*" :*/ DirectoryName + FileName; }
        }

        /// <summary>
        /// Gets or sets the query string, excluding the question mark.
        /// </summary>
        /// <value>The query string.</value>
        /// <example>var1name=val1&var2name=val2</example>
        public string Query { get; set; }

        /// <summary>
        /// Gets or sets the anchor (also known as the "reference") of this <code>URL</code> (the part after the hash).
        /// </summary>
        /// <value>The anchor (also known as the "reference").</value>
        /// <example>anchor</example>
        public string Anchor { get; set; }

        /// <summary>
        /// Gets or sets the port number.
        /// </summary>
        /// <value>The port number.</value>
        private int Port { get; set; }

        public WildcardUrl(String url) {
            Dictionary<string, string> tempUri = new Dictionary<string, string>(14);
            String[] parts = { "source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directoryname", "filename", "query", "anchor" };
            bool strictMode = false;
            Regex pattern;
            if(strictMode) {
                pattern = new Regex("^(?:([^:/?#]+):)?(?://((?:(([^:@]*):?([^:@]*))?@)?([^:/?#]*)(?::(\\d*))?))?((((?:[^?#/]*/)*)([^?#]*))(?:\\?([^#]*))?(?:#(.*))?)");
            } else {
                pattern = new Regex("^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?://)?((?:(([^:@]*):?([^:@]*))?@)?([^:/?#]*)(?::(\\d*))?)(((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))(?:\\?([^#]*))?(?:#(.*))?)");
            }

            Match match = pattern.Match(url.ToString());
            if(match.Success) {
                for(int i = 0; i < 14; i++) {
                    Group matchGroup = match.Groups[i];
                    if(matchGroup.Success) {
                        string matchValue = matchGroup.Value;
                        if(!string.IsNullOrEmpty(matchValue) && matchValue.Length > 0) {
                            tempUri.Add(parts[i], matchGroup.Value);
                        }
                    } else {
                        tempUri.Add(parts[i], "*");
                    }
                }
            }

            string protocol,
                   user,
                   password,
                   host,
                   directoryname,
                   filename,
                   query,
                   anchor;
            this.Protocol = tempUri.TryGetValue("protocol", out protocol) ? protocol : string.Empty;
            this.User = tempUri.TryGetValue("user", out user) ? user : string.Empty;
            this.Password = tempUri.TryGetValue("password", out password) ? password : string.Empty;
            this.Host = tempUri.TryGetValue("host", out host) ? host : string.Empty;
            this.DirectoryName = tempUri.TryGetValue("directoryname", out directoryname) ? directoryname : string.Empty;
            this.FileName = tempUri.TryGetValue("filename", out filename) ? filename : string.Empty;
            this.Query = tempUri.TryGetValue("query", out query) ? query : string.Empty;
            this.Anchor = tempUri.TryGetValue("anchor", out anchor) ? anchor : string.Empty;
            int port = -1;
            string rawPort;
            this.Port = tempUri.TryGetValue("port", out rawPort) && rawPort != "*" && int.TryParse(rawPort, out port) ? port : -1;
        }

        /// <summary>
        /// Gets the authority part of this <code>URL</code> (the domain).
        /// </summary>
        /// <returns>The authority part of this <code>URL</code> (the domain)</returns>
        /// <example>www.lime49.com OR user:pwd@domain.com</example>
        public String getAuthority() {
            StringBuilder auth = new StringBuilder();
            if(UserInfo != "*") {
                auth.Append(UserInfo).Append("@");
            }
            auth.AppendFormat("@{0}:{1}", Host, Port);
            return auth.ToString();
        }

        /// <summary>
        /// Determines whether or not the specified URL matches this WildcardURL.
        /// </summary>
        /// <param name="url">The URL to check.</param>
        /// <returns><code>True</code> if the protocol, domain, directory and path match the specified URL, otherwise <code>false</code></returns>
        public bool Matches(string url) {
            return Matches(new Uri(url));
        }

        /// <summary>
        /// Determines whether or not the specified URL matches this WildcardURL.
        /// </summary>
        /// <param name="url">The URL to check.</param>
        /// <returns><code>True</code> if the protocol, domain, directory and path match the specified URL, otherwise <code>false</code></returns>
        public bool Matches(Uri url) {
            bool matches = false;
            string pathWithoutQueryString = StringParser.ExtractLeft("?", HttpUtility.UrlDecode(url.PathAndQuery));
            if(WildcardMatches(Protocol, url.Scheme) && WildcardMatches(Host, url.Host) && PathMatches(pathWithoutQueryString)) {
                matches = true;
            }
            //System.out.println((wildcardMatches(protocol, url.getProtocol()) ? "t":"f") +"-"+  (wildcardMatches(host, url.getHost()) ? "t":"f") +"-" + (wildcardMatches(getPath(), url.getPath()) ? 't' : 'f'));
            return matches;
        }

        /// <summary>
        /// Gets whether the specified path matches the path of this WildcardURL.
        /// </summary>
        /// <param name="path">The path to check.</param>
        /// <returns><code>True</code> if the path matches, otherwise <code>false</code></returns>
        private bool PathMatches(String path) {
            bool match = false;
            string pathToMatch = this.Path.Length > 1 && this.Protocol == "file" ? this.Path.Substring(1) : this.Path;
            if(DirectoryName == "/" || DirectoryName == "*") { // /*file.html will not match because wildcard is in the middle
                if(WildcardMatches(pathToMatch, path.Substring(1))) {
                    match = true;
                }
            } else if(WildcardMatches(pathToMatch, path)) {
                match = true;
            }
            return match;
        }

        /// <summary>
        /// Gets whether a string matches a wildcard pattern. The following would be considered to be matches:
        /// <code>*pattern   somepattern</code>
        /// <code>pattern*   patternsome</code>
        /// <code>*pattern*  somepatternsome</code>
        /// </summary>
        /// <param name="pattern">The pattern to check, wildcards must be at either the start, end or both, but not in the middle.</param>
        /// <param name="stringToMatch">The string to match.</param>
        /// <returns><code>True</code> if the wildcard matches the pattern, otherwise <code>false</code></returns>
        private static bool WildcardMatches(String pattern, String stringToMatch) {
            bool match = false;
            if(pattern.Equals(stringToMatch, StringComparison.InvariantCultureIgnoreCase)) { // match
                match = true;
            } else if(pattern.Length > 0) {
                if(pattern[0] == '*' || pattern[0] == '/') {
                    if(pattern.Length == 1) {
                        match = true; // *
                    } else if(pattern[pattern.Length - 1] == '*' && pattern.Length > 2 && stringToMatch.Contains(pattern.Substring(1, pattern.Length -2).ToLowerInvariant())) {
                        match = true; // *match*
                    } else if(pattern.Length > 1 && stringToMatch.EndsWith(pattern.Substring(1).ToLowerInvariant())) {
                        match = true; // *match
                    }
                } else if(pattern[pattern.Length - 1] == '*' && stringToMatch.StartsWith(pattern.Substring(0, 2).ToLowerInvariant())) {
                    match = true; // match*
                }
            }
            return match;
        }

        public override string ToString() {
            return Protocol + "://" +
                   UserInfo +
                //user+(password.equals("*") ? "" : ":"+password)+(!user.equals("*") && !password.equals("*") ? "@":"")+
                   Host +
                   (Port == -1 ? "" : ":" + Port) +
                   Path +
                   Query +
                   (Anchor == "*" ? string.Empty : "#" + Anchor);
        }
    }
}